const fs = require('fs');
const Module = require('./module');
const MagicString = require('magic-string');
const path = require('path');
const {replaceIdentifiers,has} = require('./utils');

class Bundle {
  constructor(options) {
    //确保入口文件以 .js 结尾
    this.entryPath = options.entry.replace(/\.js$/, '') + '.js';
    this.statements = [];
    this.modules = {}; //存放着所有模块 (入口文件和它依赖的模块)
  }

  build(outputFileName){
    //从入口文件的绝对路径触发找到它的模块定义
    const entryModule = this.fetchModule(this.entryPath);

    //把这个入口模块所有的语句进行展开, 返回所有的语句组成的数组
    this.statements = entryModule.expandAllStatements();
    this.deConflict(); //处理变量名冲突
    const {code} = this.generate();
    fs.writeFileSync(outputFileName, code, 'utf-8');
  }

  //获取模块信息
  fetchModule(importee/*要读取的模块的路径*/,importer/*父模块*/){
    let route;

    if (!importer) { //说明是入口模块
      route = importee;
    }else { //非入口模块 需要酌情处理
      if(path.isAbsolute(importee)){
        route = importee;
      }else if(importee[0]==='.'/*取第一个字符*/){
        route = path.resolve(path.dirname(importer),importee.replace(/\.js$/, '') + '.js');
      }
    }

    if (route) {
      const code = fs.readFileSync(route, 'utf-8');

      /** ↓new的时候会生成该模块的ast,并对ast进行解析,对每一条语句进行注释*/
        //①会对模块的 this.imports、this.exports、this.definitions 进行赋值
        //②给模块的ast的每个statement（顶级语句）下挂载了 _source(该条语句对应的源码)、_defines(是否是顶级语句)、_dependsOn(是否依赖外置变量)等属性
      const module = new Module({
        code, //模块源代码
        path: route, //模块的绝对路径
        bundle: this //属于哪个bundle
      });
      return module;
    }
  }

  generate(){
    const magicString = new MagicString.Bundle();
    this.statements.forEach(statement => {

      //变量名的替换 (解决不同模块里存在相同变量名合并进来后会发生冲突的问题
      const replacements = {}; //老名字和新名字的对应关系
      Object.keys(statement._dependsOn).concat(Object.keys(statement._defines))
        .forEach(name => {
          const canonicalName/*规范名称, 这里是值重命名后的名称*/ = statement._module.getCanonicalName(name);
          if(name!==canonicalName) replacements[name] = canonicalName;
        });

      const source = statement._source.clone();

      //我们打包进来的别的模块的导出语句 需要去掉 export
      if (statement.type === 'ExportNamedDeclaration') {
        source.remove(statement.start, statement.declaration.start); // export var name = 'ahhh' ---> var name = 'ahhh'
      }

      //如果一个函数只声明 但没有被调用 则需要去掉 (这里只考虑了最外层的, 没有考虑内层
      if(statement.type === 'FunctionDeclaration'){
        const allExpressionStatements = this.statements.filter(stat => stat.type === 'ExpressionStatement'); //todo 应该是从该函数声明时所处的作用域往后找
        const allCallExpressionStatements = allExpressionStatements.filter(stat => stat.expression.type === 'CallExpression');
        if(allCallExpressionStatements.find(stat => stat.expression.callee.name === statement.id.name) === undefined) return;
      }

      //用新名字替换掉老名字
      replaceIdentifiers(statement, source, replacements);

      magicString.addSource({
        content: source,
        separator: '\n'
      });
    });
    return {code: magicString.toString()};
  }

  deConflict(){
    const defines = {}; //变量定义
    const conflicts = {}; //命名冲突
    this.statements.forEach(statement => {
      Object.keys(statement._defines).forEach(name => {
        if(has(defines,name)){
          conflicts[name] = true; //conflict.age = true
        }else {
          defines[name] = [];
        }

        defines[name].push(statement._module); //把此语句对应的模块放进去
      });
    });

    //除了第一次的 后面模块中出现的相同的名字 都要改掉 加上 `$n`
    Object.keys(conflicts).forEach(name => {
      const modules = defines[name];
      modules.shift();
      modules.forEach((module,index) => {
        const replacement = name + '$' + (index+1);
        module.rename(name, replacement);
      });
    });

  }
}

module.exports = Bundle
